package iosu.java.games.razer.characters;

import iosu.java.games.razer.RazerClient;
import iosu.java.games.razer.Track;
import iosu.java.games.razer.api.CarInfo;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.PathIterator;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;


/**
 * clase coche generica de esta heredaran los personajes del juego y cada uno
 * aportara sus caracteristicas
 * 
 * @author iosu
 * 
 */
public class Car {
	public static final int MAX_SPEED = 240;
	public static final int MAX_REVERSE_SPEED = -80;
	public static final int DRIFTING_STEERING_THRESHOLD=15;
	public static final int DRIFTING_SPEED_THRESHOLD=100;
	
	//** estos parametros cambiarian al cambiar el personaje
	// aceleracion/freno del coche
	int pedal = 7;
	// velocidad de giro del coche
	int gyrospeed = 2; // 2 grados por tick, 30 FPS, 60 grados por segundo
	
	// peso del coche
	int weight = 1400; // kg
	// dimensiones del coche
	double widthm = 1.8; // metros
	double longm = 3.8; // metros
	// ejes desde el morro
	double frontwheel = 0.5; // metros
	double rearwheel = 3.0; // metros
	// el reparto de peso del coche en resposo sobre los ejes sera
	// 700Kg en el delantero y 700Kg en el trasero
	// frenar o acelerar provocaran que este reparto varie de forma proporcional a la fuerza de frenado/accel
	// pero simplificando, al frenar repartimos 0,75-0,25 y al acelerar 0,25-0,75
	double frontwheelweight=0.5;
	double rearwheelweight=0.5;
	double frontwheelbrakingweight=0.75;
	double rearwheelbrakingweight=0.25;
	double frontwheelacceleratingweight=0.25;
	double rearwheelacceleratingweight=0.75;
	//*************************************************
	// estos son los parametros que precalculo cuando creo el coche

	//************************************************
	
	int accumulatedgyro = 0;
	int accumulatedrotatingframes = 0;
	private int lapnum=1;
	// coord X inicial de la esquina superior derecha del coche
	private int centerX = 200;
	// coord Y inicial de la esquina superior derecha del coche
	private int centerY = 200;
	
	// orientacion en grados del morro del coche 0-359 en sentido horario
	private int orientation = 0; //apunta hacia arriba
	// angulo de la direccion en la que se mueve el coche 
	// (cuando derrapa, el morro y el movimiento no coinciden)
	private int movangle = 0;
	// si el coche esta ralentizado o no
	private boolean snared = false;
	private boolean braking = false;
	// si el coche esta acelerando (para ponerle estela)
	private boolean accelerating = false;
	// si el coche esta girando
	private boolean rotatingleft = false;
	private boolean rotatingright = false;
	// estas de debajo podian ser variables de un personaje a otro
	// velocidad actual del coche
	private double currentspeed = 0;
	// velocidad actual del eje delantero
	double frontwheelcurrentspeed = 0;
	double rearwheelcurrentspeed=0;
	// si el coche tiene un arma over cerca
	private boolean opweapon = false;
	// si el coche tiene un arma activa
	private boolean availweapon = false;
	// si el coche esta frenando (para ponerle lucecita roja)
		// velocidad maxima del coche
	private int maxspeed = MAX_SPEED;
	private boolean isdrifting = false;
	int spriteWidth;
	int spriteHeight;
	double frontwheelx;
	double rearwheelx;
	double proportionalfrontwheel;
	double proportionalrearwheel;
	
	
	private String name;
	private int health;
	private CarInfo lastStatus = new CarInfo();

	// cuando este coche de las vueltas que le pide el circuito, pasara a estado "finished"
	private boolean finished = false;
	
	private Image sprite;
	
	// rectangulo para la deteccion de colisiones
	private Rectangle collision;
	
	// circuito, si es distinto de null, sera el circuito sobre el que el coche esta corriendo
	// si es null, el coche no esta corriendo en ningun circuito
	private Track track;
	
	public Car(Image image, int x, int y, String name){
		this.sprite = image;
		this.centerX=x;
		this.centerY=y;
		this.name = name;
		if(image!=null){
			spriteWidth = this.sprite.getWidth(null);
			spriteHeight = this.sprite.getHeight(null);
		}else{
			spriteWidth=0;
			spriteHeight=0;
		}
		// creo el rectangulo para las colisiones
		collision = new Rectangle(centerX,centerY,spriteWidth,spriteHeight);

		proportionalfrontwheel = (frontwheel*spriteWidth)/longm;
		proportionalrearwheel = (rearwheel*spriteWidth)/longm;
		frontwheelx = centerX + proportionalfrontwheel;
		rearwheelx = centerX + proportionalrearwheel;
		
		// empezamos con 100 de vida, vamos restando con las colisiones
		health=100;
		}

	public Image getSprite() {
		return sprite;
	}

	public void setSprite(Image sprite) {
		this.sprite = sprite;
	}

	public int getCenterX() {
		return centerX;
	}

	public void setCenterX(int centerX) {
		this.centerX = centerX;
		this.collision.setLocation(this.centerX, this.centerY);
	}

	public int getCenterY() {
		return centerY;
	}

	public void setCenterY(int centerY) {
		this.centerY = centerY;
		this.collision.setLocation(this.centerX, this.centerY);
	}

	public int getOrientation() {
		return orientation;
	}

	public void setOrientation(int orientation) {
		this.orientation = orientation;
	}

	public void update(long deltatime) {
		// deltatime contiene los milisegundos que he de actualizar
		// por el rozamiento la velocidad disminuye poco a poco
		if((0<currentspeed)&&(2>currentspeed)){
			lastStatus.setCurrentspeed((int)currentspeed);
			currentspeed = 0;
		}else{
			currentspeed*=.97;
			lastStatus.setCurrentspeed((int)currentspeed);
		}
		
		// compruebo si esta snared
		if(isSnared()){
			maxspeed = MAX_SPEED/2;
		}else{
			maxspeed = MAX_SPEED;
		}
		
		if(accelerating){
			accelerate();
		}else if(braking){
			brake();
		}

		
		if( ((currentspeed > 10)||(currentspeed < -5)) 
				&& rotatingleft ){
			rotateLeft();
		}else if( ((currentspeed > 10)||(currentspeed < -5)) 
				&& rotatingright){
			rotateRight();
		}

		
//		if(this.rotatingleft){
//			lateralvector = new Line2D.Double(this.centerX + spriteWidth/2, 
//			this.centerY+spriteHeight/2,
//			(this.centerX + spriteWidth/2)+((currentspeed/2)*Math.sin(Math.toRadians(orientation+90))),
//			(this.centerY+spriteHeight/2)-((currentspeed/2)*Math.cos(Math.toRadians(orientation+90))));
//		}else if(this.rotatingright){
//			lateralvector = new Line2D.Double(this.centerX + spriteWidth/2, 
//			this.centerY+spriteHeight/2,
//			(this.centerX + spriteWidth/2)+((currentspeed/2)*Math.sin(Math.toRadians(orientation-90))),
//			(this.centerY+spriteHeight/2)-((currentspeed/2)*Math.cos(Math.toRadians(orientation-90))));
//		}
	
		int deltax = 0;
		int deltay = 0;
		
		if(rotatingright){
			if (lastStatus.isRotatingright()){
				this.accumulatedrotatingframes++;
			}
			// si estoy girando a la izquierda calculo el derrape
			// derrapo a partir de 100km/h de velocidad y 1sg frames de giro
			// mientras derrapo, el ratio de giro baja
			if((currentspeed > DRIFTING_SPEED_THRESHOLD)&&(this.accumulatedrotatingframes>=DRIFTING_STEERING_THRESHOLD)){
				double ax = Math.sin(Math.toRadians(orientation));  
				double ay = -Math.cos(Math.toRadians(orientation));
				double aay = -Math.cos(Math.toRadians(orientation-90));
				deltax = (int)(currentspeed*ax*(deltatime/1e9));
				deltay = (int)((currentspeed*ay*(deltatime/1e9))+(((currentspeed/4)*aay*(deltatime/1e9))));
				this.gyrospeed=1;
				this.isdrifting=true;
			}else{
				double ax = Math.sin(Math.toRadians(orientation));  
				double ay = -Math.cos(Math.toRadians(orientation));
				deltax = (int)(currentspeed*ax*(deltatime/1e9));
				deltay = (int)(currentspeed*ay*(deltatime/1e9));
				this.gyrospeed=2;
				this.isdrifting = false;
			}

		}else if (rotatingleft){
			if(lastStatus.isRotatingleft()){
				this.accumulatedrotatingframes++;
			}
			// si estoy girando a la izquierda calculo el derrape
			// derrapo a partir de 100km/h de velocidad y 1sg de giro 
			if((currentspeed > DRIFTING_SPEED_THRESHOLD)&&(this.accumulatedrotatingframes>=DRIFTING_STEERING_THRESHOLD)){
				double ax = Math.sin(Math.toRadians(orientation));  
				double ay = -Math.cos(Math.toRadians(orientation));
				double aay = -Math.cos(Math.toRadians(orientation+90));
				deltax = (int)(currentspeed*ax*(deltatime/1e9));
				deltay = (int)((currentspeed*ay*(deltatime/1e9))+(((currentspeed/4)*aay*(deltatime/1e9))));
				this.gyrospeed=1;
			}else{
				double ax = Math.sin(Math.toRadians(orientation));  
				double ay = -Math.cos(Math.toRadians(orientation));
				deltax = (int)(currentspeed*ax*(deltatime/1e9));
				deltay = (int)(currentspeed*ay*(deltatime/1e9));
				this.gyrospeed=2;
			}
		}else{
			double ax = Math.sin(Math.toRadians(orientation));  
			double ay = -Math.cos(Math.toRadians(orientation));
			deltax = (int)(currentspeed*ax*(deltatime/1e9));
			deltay = (int)(currentspeed*ay*(deltatime/1e9));
			this.isdrifting = false;
		}

		
		
		
		// si track es null, este no es el coche del jugador
		// asi q lo muevo sin mas
		if(this.track==null){
			centerX += (deltax);
			centerY += (deltay);
			this.collision.setLocation(this.centerX, this.centerY);
		}else{
			// si no, si que es el coche del jugador
			// y verifico que ninguno de sus vertices termine sobre color rojo
			//		o (centerx,centery)                 o (centerx+spritewidth,centery)
			//
			//      o (centerx,centery+spriteheight)    o (centerx+spritewidth,centery+spriteheight)
			if((this.track.getColorAt(centerX+deltax, centerY+deltay)==Track.TRACK_RED)
					|| (this.track.getColorAt(centerX+spriteWidth+deltax,centerY+deltay)==Track.TRACK_RED)
					|| (this.track.getColorAt(centerX+deltax, centerY+spriteHeight+deltay)==Track.TRACK_RED)
					|| (this.track.getColorAt(centerX+spriteWidth+deltax, centerY+spriteHeight+deltay)==Track.TRACK_RED)){
				// si me voy a mover a un rojo, no me muevo
				
			}else{
				//  separado por x e y
				// primero x
				if(this.track.getColorAt(centerX+deltax, centerY)!=Track.TRACK_RED){
					// no es rojo, lo muevo
					if((centerX+deltax>10)&&(centerX+deltax<790)){
						lastStatus.setCenterx(this.centerX);
						centerX += deltax;
						this.collision.setLocation(this.centerX, this.centerY);
					}
				}
				// despues y 
				if(this.track.getColorAt(centerX, centerY+deltay)!=Track.TRACK_RED){
					// no es rojo, lo muevo
					if((centerY+deltay>10)&&(centerY+deltay<470)){
						lastStatus.setCentery(this.centerY);
						centerY += deltay;
						this.collision.setLocation(this.centerX, this.centerY);
					}
				}
			}
			// ya he movido el coche
			// si he llegado a alguna zona amarilla, compruebo si es mi nextMilestone
			
			if((this.track.getColorAt(centerX, centerY)==Track.TRACK_YELLOW)
					||(this.track.getColorAt(centerX, centerY)==Track.TRACK_BLUE)){
				if(this.track.checkMilestone(centerX, centerY)){
					// miro si es la ultima vuelta o no
					if(this.lapnum==this.track.getNumberoflaps()){
						// este coche ha terminado la carrera
						this.finished = true;
					}else{
						//  no ha terminado, sumo otra vuelta
						this.lapnum++;
					}
				}
			
			}
		}
		
		
	}
	
	
	public void _update(CarInfo cs){
		lastStatus.setAccelerating(this.accelerating);
		lastStatus.setBraking(this.braking);
		lastStatus.setCenterx(this.centerX);
		lastStatus.setCentery(this.centerY);
		lastStatus.setCurrentspeed((int)this.currentspeed);
		lastStatus.setRotatingleft(this.rotatingleft);
		lastStatus.setRotatingright(this.rotatingright);
		lastStatus.setRotation(this.orientation);
		lastStatus.setSnared(this.snared);
		this.centerX = cs.getCenterx();
		this.centerY = cs.getCentery();
		this.currentspeed = cs.getCurrentspeed();
		this.orientation = cs.getRotation();
		this.accelerating = cs.isAccelerating();
		this.braking = cs.isBraking();
		this.rotatingleft = cs.isRotatingleft();
		this.rotatingright = cs.isRotatingright();
	}
	
	public boolean hasmoved(){
		if(this.centerX==lastStatus.getCenterx() &&
				this.centerY==lastStatus.getCentery() &&
				this.orientation==lastStatus.getRotation()){
			return false;
		}else{
			return true;
		}
	}
	
	public void paint(Graphics2D g, RazerClient razerClient){
		// la esquina superior derecha del coche esta en centerX, centerY
		// el centro del coche esta en centerx+spriteWidth/2, centery+spriteHeight/2
		// con lo que los ejes estan en
		// dimensiones del coche
		// double widthm = 1.8; // metros -> pixelheight
		// double longm = 3.8; // metros -> pixelwidth
		// ejes desde el morro
		// double frontwheel = 0.5; // metros -> longm
		// double rearwheel = 3.0; // metros
		// asi q los puntos de los ejes estan en
		// delantero: centerX+(int)proportionalfrontwheel
		// trasero: centerX+(int)proportionalrearwheel
		
		// y aplico la transformacion al sprite

		AffineTransform affineTransform = new AffineTransform();
		affineTransform.rotate(Math.toRadians(orientation+90),
//		affineTransform.rotate(Math.toRadians(orientation),
				this.centerX + spriteWidth / 2, this.centerY + spriteHeight / 2);
		affineTransform.translate(this.centerX, this.centerY);
	    g.drawImage(sprite, affineTransform, razerClient);
	    g.setColor(Color.CYAN);
//	 	collision.setBounds(this.centerX, this.centerY, spriteWidth, spriteHeight);
	 	AffineTransform at = new AffineTransform();
	 	at.rotate(Math.toRadians(orientation),
	 	this.centerX + spriteWidth / 2, this.centerY + spriteHeight / 2);
	 	g.draw(at.createTransformedShape(collision.getBounds2D()));
//	 	g.draw(collision.getBounds2D());
	    frontwheelx = centerX + proportionalfrontwheel;
		rearwheelx = centerX + proportionalrearwheel;
		
//	    Line2D frontwheelline = new Line2D.Double(frontwheelx,this.centerY, 
//	    		frontwheelx, this.centerY+spriteHeight);
//		Line2D rearwheelline = new Line2D.Double(rearwheelx,this.centerY, 
//				rearwheelx, this.centerY+spriteHeight);
		
		// el vector de direccion: rotacion*currentspeed
//		Line2D speedvector = new Line2D.Double(this.centerX + spriteWidth/2, 
//				this.centerY+spriteHeight/2,
//				(this.centerX + spriteWidth/2)+((currentspeed/2)*Math.sin(Math.toRadians(orientation))),
//				(this.centerY+spriteHeight/2)-((currentspeed/2)*Math.cos(Math.toRadians(orientation))));
//
		// si el coche esta girando, aparece la fuerza lateral de la inercia
		// si giro para la izquierda, la fuerza aparece por la derecha
		// si giro para la derecha, la fuerza aparece por la izq
		
//		Line2D lateralvector = null;
//		if(this.rotatingleft){
//			lateralvector = new Line2D.Double(this.centerX + spriteWidth/2, 
//			this.centerY+spriteHeight/2,
//			(this.centerX + spriteWidth/2)+((currentspeed/2)*Math.sin(Math.toRadians(orientation+90))),
//			(this.centerY+spriteHeight/2)-((currentspeed/2)*Math.cos(Math.toRadians(orientation+90))));
//		}else if(this.rotatingright){
//			lateralvector = new Line2D.Double(this.centerX + spriteWidth/2, 
//			this.centerY+spriteHeight/2,
//			(this.centerX + spriteWidth/2)+((currentspeed/2)*Math.sin(Math.toRadians(orientation-90))),
//			(this.centerY+spriteHeight/2)-((currentspeed/2)*Math.cos(Math.toRadians(orientation-90))));
//		}
		
		// los ejes seran
		// (frontwheelx,y) (frontwheelx,y+spriteheight) (rearwheelx, y) (rearwheelx,y+spriteheight)
		// el centro de cada eje sera:
		// (frontwheelx, (y+spriteheight/2)) y (rearwheelx, (y+spriteheight/2))
		
//		Point2D frontwheelcenter = new Point2D.Double(frontwheelx,(double)(this.centerY+spriteHeight/2));
//		Point2D rearwheelcenter = new Point2D.Double(rearwheelx,(double)(this.centerY+spriteHeight/2));
		
		// en rojo el centro de gravedad del coche
//	    g.setColor(Color.RED);
//	    g.drawOval(this.centerX+spriteWidth/2, this.centerY+spriteHeight/2,  1,  1);
	    // rectangulo de colision
	 	
//	 	g.draw(affineTransform.createTransformedShape(new Rectangle2D.Double(frontwheelcenter.getX(),
//	 			frontwheelcenter.getY(), 1, 1)));
//	 	g.draw(affineTransform.createTransformedShape(new Rectangle2D.Double(rearwheelcenter.getX(),
//	 			rearwheelcenter.getY(), 1, 1)));
//	 	g.draw(affineTransform.createTransformedShape(frontwheelline));
//	 	g.draw(affineTransform.createTransformedShape(rearwheelline));
//	 	g.setColor(Color.YELLOW);
//	 	g.draw(speedvector);
//	 	if(lateralvector!=null) g.draw(lateralvector);
//	 	g.setColor(Color.BLUE);
// 		g.draw(collision.getBounds2D());
// 		g.drawString(String.valueOf(this.accumulatedrotatingframes), 50, 50);
 		// si esta derrapando, pinto los pixels debajo de las ruedas de negro
 		
	}

	// metodo que gira el coche hacia la derecha 0-360
	public void rotateRight() {
		this.lastStatus.setRotatingright(this.rotatingright);
		this.rotatingright=true;
		this.lastStatus.setRotation(orientation);
		// sumo a orientation el valor de gyrospeed * 10 (son 360 grados!!)
		if(orientation+gyrospeed>360){
			orientation = orientation+gyrospeed-360;
		}else{
			orientation += gyrospeed;
		}

	}

	// metodo que gira el coche hacia la izquierda 360-0
	public void rotateLeft() {
		this.lastStatus.setRotatingleft(this.rotatingleft);
		this.rotatingleft=true;
		this.lastStatus.setRotation(orientation);
		// si es menor que 0, no lo hago negativo, sino resto de 360
		if(orientation-gyrospeed<0){
			orientation = 360-gyrospeed;
		}else{
			orientation -=gyrospeed;
		}
		
	}

	// metodo que acelera el coche
	public void accelerate() {
		this.lastStatus.setAccelerating(this.accelerating);
		accelerating = true;
		this.lastStatus.setCurrentspeed((int)currentspeed);
		if (currentspeed + pedal <= maxspeed) {
			currentspeed += pedal;
		} else {
			// si ya voy a tope no acelero mas
		}

	}

	// metodo que frena el coche
	public void brake() {
		this.lastStatus.setBraking(this.braking);
		braking = true;
		this.lastStatus.setCurrentspeed((int)currentspeed);
		// TODO freno incremental *0.9?
		if (currentspeed - pedal < MAX_REVERSE_SPEED) {
			// la marcha atras max. llega a -5
		} else {
			currentspeed -= pedal;
		}
	}


		

	public boolean isSnared() {
		return snared;
	}

	public void setSnared(boolean sna) {
		this.lastStatus.setSnared(this.snared);
		this.snared = sna;
	}


	public boolean isBraking() {
		return braking;
	}

	public void setBraking(boolean braking) {
		this.lastStatus.setBraking(this.braking);
		this.braking = braking;
	}

	public boolean isAccelerating() {
		return accelerating;
	}

	public void setAccelerating(boolean accelerating) {
		this.lastStatus.setAccelerating(this.accelerating);
		this.accelerating = accelerating;
	}


	public double getCurrentspeed() {
		return currentspeed;
	}

	public void setCurrentspeed(double currentspeed) {
		this.lastStatus.setCurrentspeed((int)this.currentspeed);
		this.currentspeed = currentspeed;
	}


	public boolean isRotatingleft() {
		return rotatingleft;
	}

	public void setRotatingleft(boolean rotatingleft) {
		if(!rotatingleft) this.accumulatedrotatingframes = 0;
		this.lastStatus.setRotatingleft(this.rotatingleft);
		this.rotatingleft = rotatingleft;
	}

	public boolean isRotatingright() {
		return rotatingright;
	}

	public void setRotatingright(boolean rotatingright) {
		if(!rotatingright) this.accumulatedrotatingframes = 0;
		this.lastStatus.setRotatingright(this.rotatingright);
		this.rotatingright = rotatingright;
	}

	public String getName() {
		return name;
	}

	public Track getTrack() {
		return track;
	}

	public void setTrack(Track track) {
		this.track = track;
	}

	public int getLapnum() {
		return lapnum;
	}

	public void setLapnum(int lapnum) {
		this.lapnum = lapnum;
	}

	public boolean isFinished() {
		return finished;
	}

	public void setFinished(boolean finished) {
		this.finished = finished;
	}

	public boolean isDrifting() {
		return isdrifting;
	}
	
	public int[][] getWheelCoords(){
		return new int[][]{{(int)frontwheelx,this.centerY},{(int)frontwheelx,this.centerY+spriteHeight},
				{(int)rearwheelx,this.centerY},{(int)rearwheelx,this.centerY+spriteHeight},
		 {(int)(this.lastStatus.getCenterx()+proportionalfrontwheel),this.lastStatus.getCentery()},
		 {(int)(this.lastStatus.getCenterx()+proportionalfrontwheel),this.lastStatus.getCentery()+spriteHeight},
		 {(int)(this.lastStatus.getCenterx()+proportionalrearwheel), this.lastStatus.getCentery()},
		 {(int)(this.lastStatus.getCenterx()+proportionalrearwheel), this.lastStatus.getCentery()+spriteHeight}};
	}
}
